#include "Directory.h"
#include "DirectoryException.h"
#include <math.h>
#include "Util.h"
#include "MasterBlock.h"
#include "IntArray.h"


using namespace std;

/*
 * Directory.cpp
 *
 * Created on March 8, 2005, 8:29 PM
 * Modified: CGP; 2/5/09
 *      Converted to C++ from Java.
 */

Directory::Directory(FreeList *freeList, bool create) {

    m_freeList = freeList;

    // don't need to subtract 4 from GetBlockSize() 
    // for the BlockLinkedList class- this method
    // already takes off four.
    ENTRIES_PER_BLOCK = 
            (m_freeList->GetBlockSize()) / DirectoryEntry::BYTES_PER_DIRECTORY_ENTRY;
    printf("Directory: BlockSize (from freelist)= %d\n", m_freeList->GetBlockSize());
    printf("Directory: Bytes per directory entry= %d\n", 
        DirectoryEntry::BYTES_PER_DIRECTORY_ENTRY);

    if (ENTRIES_PER_BLOCK == 0) {
        printf("Directory: ERROR: Attempting to store 0 entries per block!\n");
        printf("Directory: ERROR: free list block size= %d\n", m_freeList->GetBlockSize());
        printf("Directory: ERROR: bytes per directory entry = %d\n",
                DirectoryEntry::BYTES_PER_DIRECTORY_ENTRY);
        throw DirectoryException();
    }

    printf("Directory: Storing %d entries per block.\n", ENTRIES_PER_BLOCK);

    // Read the current MasterBlock from disk. Even if there is no Directory yet,
    // this is OK. That will get resolved below.
    m_dmBlock = new FSBlock(0, m_freeList->GetDisk());

    if (create) {
        CreateDirectory();
    } else {
        // Open an existing directory
        OpenDirectory();
    }
}

/** Opens an existing directory. It must have been created before using the
 * Directory constructor with the create=true setting. Reads the start & end block
 * numbers from the master block, and reads the number of files currently represented
 * by the directory.
 */
void Directory::OpenDirectory() {
    if (m_freeList == NULL) {
        printf("Directory.OpenDirectory: FreeList has null pointer\n");
        fflush(stdout);
        return;
    }

    // Let the start/end block numbers be 0 just in case students have
    // 0 block directories.
    unsigned int startBlock = m_dmBlock->GetInt(MasterBlock::DIRECTORY_START_BLOCK_NUMBER);
    if (((int) startBlock < 0) || (startBlock > (m_freeList->GetDisk()->numberOfBlocks()-1))) {
        printf("Directory.OpenDirectory: ERROR: startBlock= %d\n", startBlock);
        fflush(stdout); return;
    }

    unsigned int endBlock = m_dmBlock->GetInt(MasterBlock::DIRECTORY_END_BLOCK_NUMBER);
    if (((int) endBlock < 0) || (endBlock > (m_freeList->GetDisk()->numberOfBlocks()-1))) {
        printf("Directory.OpenDirectory: ERROR: endBlock= %d\n", endBlock);
        fflush(stdout); return;
    }

    unsigned int numberOfBlocks = 
        m_dmBlock->GetInt(MasterBlock::DIRECTORY_CURR_NUMBER_BLOCKS);
    // Allow a directory to have 0 blocks in case the students create empty directory
    // as having 0 blocks.
    if (((int) numberOfBlocks < 0) || 
            (numberOfBlocks > (m_freeList->GetDisk()->numberOfBlocks()))) {
        printf("Directory.OpenDirectory: ERROR: numberOfBlocks= %d\n", numberOfBlocks);
        fflush(stdout); return;
    }

    unsigned int numberOfFiles = 
            m_dmBlock->GetInt(MasterBlock::DIRECTORY_CURR_NUMBER_FILES);
    if ((int) numberOfFiles < 0) {
        printf("Directory.OpenDirectory: ERROR: numberOfFiles= %d\n", numberOfFiles);
        fflush(stdout); return;
    }

    if ((numberOfBlocks == 0) && (numberOfFiles != 0)) {
        printf("Directory.OpenDirectory: ERROR: zero blocks but > 0 files!\n");
        fflush(stdout); return;
    }

    /* I'm going to allow more than 0 blocks if there are zero files because it is possible
        that (a) 1 block could be allocated for an empty directory and (b)
        that some number of blocks were allocated prevously, but the directory size
        went to 0 and the blocks were not deallocated. E.g., if the students were using
        a strategy of not reclaiming blocks when the directory size was reduced.
    
    if ((numberOfBlocks != 0) && (numberOfFiles == 0)) {
        printf("Directory.OpenDirectory: ERROR: zero files but > 0 blocks!\n");
    }
    */

    // Now, let's see if we have enough blocks for the files indicated.
    unsigned int estimatedNumBlocks = (int) ceil(numberOfFiles/ENTRIES_PER_BLOCK);
    if (numberOfBlocks < estimatedNumBlocks) {
        printf("Directory.OpenDirectory: ERROR: insufficient blocks (= %d) for %d files\n",
            numberOfBlocks, numberOfFiles);
        fflush(stdout); return;
    }

    /* SANITY: Determine if the number of files is way too large */

    if (numberOfFiles > (FreeList::DEFAULT_NUMBER_OF_BLOCKS-1)*ENTRIES_PER_BLOCK) {
        printf("Directory.OpenDirectory: ERROR: Waaaayyyyyy too many files (= %d)!\n",
            numberOfFiles);
        fflush(stdout); return;
    }

    printf("Directory.OpenDirectory: directory has: %d files.\n", numberOfFiles);
    printf("Directory.OpenDirectory: start Block number is: %d.\n", startBlock);
    printf("Directory.OpenDirectory: end Block number is: %d.\n", endBlock);
    printf("Directory.OpenDirectory: number of blocks: %d.\n", numberOfBlocks);

    if (startBlock != 0) {
        printf("Directory BlockGroup: ");
        unsigned int testEnd = FreeList::TestBlockGroup(startBlock, m_freeList);
        if (testEnd != endBlock) {
            printf("Directory.OpenDirectory: ERROR: actual endBlock is= %d\n", testEnd);
            fflush(stdout); return;
        }
    }
    
    if (numberOfBlocks == 0) {
        // Don't attempt to open the BlockGroup for the Directory if there are 0 blocks
        // in the Directory.
        fflush(stdout); return;
    }

    m_directory = new BlockGroup(startBlock, endBlock, numberOfBlocks, m_freeList);

    fflush(stdout);

    FSBlock *currBlk = NULL;
    int entryIndex = 0;
    unsigned char *blockData = NULL;
    int blockOffset = 0;

    // Now, need to read the current contents of the directory from 
    // Disk into our Vector.

    for (unsigned int currFile=0; currFile < numberOfFiles; currFile++) {
        // See if we need to advance to the next block of the Directory BlockGroup.
        if ((currFile % ENTRIES_PER_BLOCK) == 0) {
            if (currFile == 0) {
                m_directory->Rewind();
            } else {
                m_directory->GetNextBlock();
            }

            currBlk = m_directory->GetCurrentBlock();
            blockData = currBlk->GetBuffer();
            entryIndex = 0;
        }

        DirectoryEntry *entry = new DirectoryEntry();
        
        // Read the current entry from the current block
        // Data is stored in order: name, FCBnumber.
        // The + 4 is for the next pointer of the block.

        // start of current directory entry in the block
        blockOffset = entryIndex * DirectoryEntry::BYTES_PER_DIRECTORY_ENTRY + sizeof(int);

        bool gotNULL = false; // did we get a NULL terminator at end of file name?

        // The first DirectoryEntry::MAX_FILE_NAME_LENGTH+1 bytes are the
        // file name
        for (int i=0; i < DirectoryEntry::MAX_FILE_NAME_LENGTH + 1; i++) {
            // Copy each of the possible characters of the file name into the m_name 
            // for the current directory entry.
            entry->m_name[i] = blockData[blockOffset + i];
            // Can stop copying when we hit the NULL.
            if ('\0' == entry->m_name[i]) {
                gotNULL = true;
                break;
            }
        }

        if (! gotNULL) {
            entry->m_name[DirectoryEntry::MAX_FILE_NAME_LENGTH] = '\0';
            printf("Directory.OpenDirectory: ERROR: Directory block");
            printf(" did not have a NULL for directory entry %d!\n", entryIndex);
            fflush(stdout); return;
        }

        // Now, get the FCB block number out of the directory entry
        // for current directory entry
        entry->m_fcbBlockNumber = 
            IntArray::GetInt(
                blockData + blockOffset + DirectoryEntry::MAX_FILE_NAME_LENGTH + 1, 0);

        if (((int) entry->m_fcbBlockNumber < 0) || 
            (entry->m_fcbBlockNumber > (m_freeList->GetDisk()->numberOfBlocks()-1))) {
            printf("Directory.OpenDirectory: ERROR: Bad FCB ");
            printf("(= %d) for directory entry %d\n", entry->m_fcbBlockNumber, 
                entryIndex);
            fflush(stdout); return;
        }
        
        //blockOffset += sizeof(int);

        printf("Directory.OpenDirectory: " ); entry->Output(true);

        m_entries.push_back(entry);
        entryIndex++;
    }
}

/** Creates a new BlockGroup for the directory, and writes:
 *      the start and end block;
 *      the number of blocks in the directory;
 *      and the number of files
 * for that block group to the master block.
 */
void Directory::CreateDirectory() {
    printf("Directory.CreateDirectory: Attempting to create new BlockGroup for Directory\n"); fflush(stdout);
    m_directory = m_freeList->CreateNew();

    printf("Directory.CreateDirectory:");
    printf("Attempting to update MasterBlock with Directory information\n");
    UpdateMasterBlock();
}

/** Update master block with the current information about the m_directory BlockGroup.
 */
void Directory::UpdateMasterBlock() {
    m_dmBlock->SetInt(m_directory->GetStartBlockNumber(),
            MasterBlock::DIRECTORY_START_BLOCK_NUMBER);
    m_dmBlock->SetInt(m_directory->GetEndBlockNumber(),
            MasterBlock::DIRECTORY_END_BLOCK_NUMBER);
    m_dmBlock->SetInt(m_directory->GetNumberOfBlocks(),
            MasterBlock::DIRECTORY_CURR_NUMBER_BLOCKS);
    m_dmBlock->SetInt(m_entries.size(),
            MasterBlock::DIRECTORY_CURR_NUMBER_FILES);

    m_dmBlock->Write(m_freeList->GetDisk());

    printf("Directory.UpdateMasterBlock: directory has: %d files.\n", (int) m_entries.size());

    printf("Directory.UpdateMasterBlock: start block number of Directory is: %d.\n",
        m_directory->GetStartBlockNumber());
    printf("Directory.UpdateMasterBlock: end block number of Directory is: %d.\n",
        m_directory->GetEndBlockNumber());
    printf("Directory.UpdateMasterBlock: number of blocks in Directory: %d.\n",
        m_directory->GetNumberOfBlocks());
}

/** This deletes the directory for a disk. After this call, no Disk blocks are allocated
 * for the directory. It is your responsiblity to delete any BlockGroups associated with
 * files stored as entries in the Directory.
 */
void Directory::Delete() {
    //m_directory.Free(m_freeList);
    if (m_directory) {
        m_freeList->Return(m_directory);
        delete m_directory;
        m_directory = NULL;
    }
    
    DeleteCache();
    
    if (m_dmBlock) {
        delete m_dmBlock;
        m_dmBlock = NULL;
    }
}

/** Flushes the Directory to the Disk. This must be called to maintain integrity of
 * the Directory structure. In addition to writing out the directory entries to the
 * blocks of the directory BlockGroup, this updates the master block with the
 * information for the m_directory.
 */
void Directory::Flush() {
    // Need to determine if we have enough blocks on disk for the Directory.
    // If not, need to allocate more.

    // How many entries do we have?
    int numberOfEntries = m_entries.size();

    // How many blocks do we need on Disk for the Directory entries?
    int numberOfBlocksNeeded =
            (int) ceil((float) numberOfEntries/(float)ENTRIES_PER_BLOCK);

    printf("Directory.Flush: Writing %d directory entries to disk.\n", numberOfEntries);
    printf("Directory.Flush: Need %d blocks at %d entries per block.\n",
            numberOfBlocksNeeded, ENTRIES_PER_BLOCK);

    // Let's unlink the current directory BlockGroup, then reallocate it.
    
    if (m_directory) {
        m_freeList->Return(m_directory);
        printf("Directory.Flush: Finished calling m_freeList->Return\n"); fflush(stdout);
        delete m_directory;
        m_directory = NULL;
    }
    
    printf("Directory.Flush: Just before CreateDirectory\n"); fflush(stdout);
    CreateDirectory();

    // start this loop at 1 because our BlockGroups start life
    // with 1 block.
    for (int i=1; i < numberOfBlocksNeeded; i++) {
        if (! m_directory->AddBlock()) {
            printf("Directory.Flush: Failed adding block #%d\n", i);
            return;
        }
    }

    int numberOfBlocksNow = m_directory->GetNumberOfBlocks();

    printf("Directory.Flush: Currently have %d blocks.\n", numberOfBlocksNow);

    // Now, have just the right number of blocks in the directory.
    // So, write out the directory entries to the Block in the Directory.
    m_directory->Rewind();

    for (int blockNum=0; blockNum < numberOfBlocksNow; blockNum++) {
        FSBlock *currBlk = m_directory->GetCurrentBlock();
        unsigned char *buffer = currBlk->GetBuffer();
        int bufferOffset = sizeof(int); // start at 4 bytes, to skip next pointer

        // Put the entries into the buffer of the current block

        for (int entryOffset=0; entryOffset < ENTRIES_PER_BLOCK; entryOffset++) {
            unsigned int entry = blockNum*ENTRIES_PER_BLOCK + entryOffset;

            if (entry == m_entries.size()) {
                // We must be on the last block, and it wasn't full
                break;
            }

            DirectoryEntry *e = m_entries[entry];

            // Put current directory entry into the buffer

            // First, put the name into the first 42 bytes at the bufferOffset
            // byte[] name = e.m_name.getBytes();

            printf("Directory.Flush: entry# %d, name = %s\n",
                    entry, e->m_name);

            for (int i=0; i < DirectoryEntry::MAX_FILE_NAME_LENGTH+1; i++) {
                buffer[bufferOffset] = e->m_name[i];
                bufferOffset++;
            }

            // Now, write the block number of the FCB
            IntArray::SetInt(buffer + bufferOffset, e->m_fcbBlockNumber, 0);
            bufferOffset += sizeof(int);

            printf("Directory.Flush: entry# %d, start block = %d\n",
                    entry, e->m_fcbBlockNumber);
        }

        m_directory->Replace(currBlk);
        m_directory->GetNextBlock();
    }

    UpdateMasterBlock();
    
}

/** Close the directory. Similar to Flush(), except that you cannot use the
 * Directory after this call.
 */
void Directory::Close() {
    Flush();
    

    DeleteCache();

    
    if (m_directory) {	
        delete m_directory;
        m_directory = NULL;
    }
    if (m_dmBlock) {
        delete m_dmBlock;
        m_dmBlock = NULL;
    }
}

/** Returns the vector of directory entries currently contained in the directory.
 * The vector will have size() 0 if there are no entries.
 * A reference to the data structure
 * contained internally is returned. The caller should not modify this data structure.
 *
 * @returns a pointer to an STL vector of DirectoryEntry objects.
 */
vector<DirectoryEntry *> *Directory::GetEntries() {
    return &m_entries;
}

/** Lookup a directory entry. If the entry is not found, null is returned.
 */
DirectoryEntry *Directory::LookUp(const char *name) {
    int pos = LookUpByPosition(name);
    if (pos == -1) return NULL;
    return m_entries[pos];
}

/** Returns true iff a directory entry could be found.
 */
bool Directory::FindEntry(const char *name) {

    int pos = LookUpByPosition(name);


    if (pos == -1) return false;
    return true;
}

/** Lookup a directory entry. If the entry is not found, -1 is returned.
 *
 * @return the m_entries index for the directory entry, if found.
 */
int Directory::LookUpByPosition(const char *name) {
    for (unsigned int i=0; i < m_entries.size(); i++) {
        DirectoryEntry *e = m_entries[i];
        if (strcmp(e->m_name, name) == 0) {
            return i;
        }
    }

    return -1;
}

/** Deletes all DirectoryEntry's in the m_entries cache.
 */
void Directory::DeleteCache() {
    for (unsigned int i=0; i < m_entries.size(); i++) {
        delete m_entries[i];
    }

    m_entries.clear();
}

/** Creates a new entry in the Directory, for a file. The caller is responsible
 * for creating a BlockGroup for the file, and keeping the info in the Directory
 * synchronized with the info in the file's BlockGroup.
 *
 * The name given must be unique
 * across all of the other file names that are presently in the directory.
 * The caller is responsible for allocating the FCB for the file (i.e., for changing
 * the FCB part of the DirectoryEntry to initial 0/NULL values.
 *
 * @return new DirectoryEntry iff entry could be added. If it could not be added
 *      returns NULL (e.g., if the name was already in the Directory). The Directory
 *      class retains ownership of the DirectoryEntry object that is returned. That is,
 *      the caller should not delete this object.
 */
DirectoryEntry *Directory::CreateNewEntry(const char *name) {
    int len = strlen(name);
    if ((len == 0) || (len > DirectoryEntry::MAX_FILE_NAME_LENGTH) ||
            (LookUp(name) != NULL)) {
        return NULL;
    }

    DirectoryEntry *entry = new DirectoryEntry(name);
    m_entries.push_back( entry);
    return entry;
}

/** Update the info for entry.m_name with the info in the DirectoryEntry passed
 * as the parameter.
 *
 * Doesn't write the info to the Disk, but just updates the data structures of
 * this class.
 *
 * This is important to do when the user of this class
 * (a) creates a BlockGroup for the file represented by the entry, and
 * (b) modifies the file's BlockGroup (e.g., adds a Block).
 */
/*
void SynchEntry(DirectoryEntry entry) {
    DirectoryEntry e = LookUp(entry.m_name);
    if (e == NULL) {
        throw DirectoryException();
    }

    e.Copy(entry);
}
*/

/** Deletes a file entry in the directory. Does not update the directory on Disk;
 * you must call Flush or Close to do that.
 *
 * @return true iff entry could be deleted.
 */
bool Directory::DeleteEntry(const char *name) {
    int pos = LookUpByPosition(name);
    if (pos == -1) {
            return false;
    }

    DirectoryEntry *e = m_entries[pos];

    m_entries.erase (m_entries.begin()+pos);

    delete e;
    return true;
}

/** Rename a file. Doesn't write the modified directory to the Disk. Call Flush
 * or Close to do that.
 *
 * @return true iff entry could be renamed.
 */
bool Directory::RenameEntry(char *oldName, char *newName) {
    int newLen = strlen(newName);
    
    if ((newLen == 0) || (newLen > DirectoryEntry::MAX_FILE_NAME_LENGTH)
        || (LookUp(newName) != NULL)) {
        return false;
    }

    DirectoryEntry *e = LookUp(oldName);
    if (e == NULL) {
        return false;
    }

    // Now, just replace the old name with the new name.
    strcpy(e->m_name, newName);
    return true;
}

/* To be used from a web page to test the binary format of file system.
 */
void Directory::WebTest(const char *filename) {    
    printf("Directory::WebTest: Starting... (using file name= %s)\n", filename);

    Disk *disk = new Disk(filename, 
        FreeList::DEFAULT_NUMBER_OF_BLOCKS, Disk::DEFAULT_BLOCK_SIZE, 
        false /* not a physical device */);

    FreeList *mother = new FreeList(disk, false); 
    FreeList::Check(mother);

    Directory *dir = new Directory(mother, false);   
    delete dir;  
    delete mother;
}

void Directory::Test() {
    //char *s = NULL;
    const char *deviceName = "disk1";
    //int numberOfBlocks = 1000;
    int cmd = 0;
    //Disk *disk1 = NULL;
    //int blockNumber = 0;
    //int byteToWrite;
    //unsigned char *block = new unsigned char[Disk::DEFAULT_BLOCK_SIZE];
    FreeList *mother = NULL;
    Directory *dir = NULL;
    char name[256];
    char name2[256];
    DirectoryEntry *entry;
    int fcb;

    Disk *disk = new Disk(deviceName, 
        FreeList::DEFAULT_NUMBER_OF_BLOCKS, Disk::DEFAULT_BLOCK_SIZE, 
        false /* not a physical device */);

    for (;;) {
        printf("\nmain: Enter command:\n");
        printf("   0) Exit program\n");
        printf("   1) create and format FreeList\n");
        printf("   2) open and don't format FreeList\n");
        printf("   3) close FreeList\n");
        printf("   4) output block numbers in FreeList\n");
        printf("\n");
        printf("   5) Create a new directory\n");
        printf("   6) Open an existing directory\n");
        printf("   7) Close directory\n");
        printf("   8) Add directory entry\n");
        printf("   9) List current entries\n");
        printf("   10) Delete an entry\n");
        printf("   11) Rename an entry\n");
        printf("   12) Delete directory\n");

        printf("Enter command: ");
        ScanfTest("%d", &cmd);

        switch (cmd) {
            case 0:
                return;

            case 1: // create and format freelist
                mother = new FreeList(disk, true);
                break;

            case 2:
                mother = new FreeList(disk, false);
                break;

            case 3:
                mother->Close();
                break;

            case 4:
                mother->Output();
                break;

            case 5:
                dir = new Directory(mother, true);
                break;

            case 6:
                dir = new Directory(mother, false);
                break;

            case 7: // Close the directory if it's open
                if (dir == NULL) {
                    printf("main: ERROR: dir is not open!\n");
                    break;
                }

                dir->Close();
                printf("main: SUCCESS: dir is now closed.\n");

                dir = NULL;
                break;

            case 8: { // Create a directory entry
                    if (dir == NULL) {
                        printf("main: ERROR: dir is not open!\n");
                        break;
                    }

                    printf("Please enter name of new entry (file name): ");
                    ScanfTest("%s", name);

                    printf("Enter FCB number: ");
                    ScanfTest("%d", &fcb);

                    DirectoryEntry *entry = dir->CreateNewEntry(name);
                    if (entry) {
                        printf("SUCCESS: Created new directory entry!\n");
                        entry->m_fcbBlockNumber = fcb;
                    } else {
                        printf("ERROR: Could not create new directory entry\n");
                    }
                }
                break;

            case 9:
                {
                    if (dir == NULL) {
                        printf("main: ERROR: dir is not open!\n");
                        break;
                    }

                    vector<DirectoryEntry *> *entries = dir->GetEntries();
                    printf("List of directory entries:\n"); fflush(stdout);
                    for (unsigned int i = 0; i < entries->size(); i++) {
                        printf("Entry %d: ", i); fflush(stdout);
                        entry = (*entries)[i];
                        entry->Output(true);
                    }
                }
                break;

            case 10: // Remove a directory entry
                if (dir == NULL) {
                    printf("main: ERROR: dir is not open!\n");
                    break;
                }

                printf("Please enter name of entry (file) to delete: ");
                ScanfTest("%s", name);

                if (dir->DeleteEntry(name)) {
                    printf("main: SUCCESS: deleted entry.\n");
                } else {
                    printf("main: ERROR: Could not delete entry.\n");
                }
                break;

            case 11: // Rename a directory entry
                if (dir == NULL) {
                    printf("main: ERROR: dir is not open!\n");
                    break;
                }

                printf("Please enter name of entry (file) to rename: ");
                ScanfTest("%s", name);

                printf("Please enter new name of entry (file): ");
                ScanfTest("%s", name2);

                if (dir->RenameEntry(name, name2)) {
                    printf("main: SUCCESS: renamed entry.\n");
                } else {
                    printf("main: ERROR: Could not rename entry.\n");
                }
                break;

            case 12: // Delete directory
                if (dir == NULL) {
                    printf("main: ERROR: dir is not open!\n");
                    break;
                }

                dir->Delete();
                printf("main: SUCCESS: deleted directory.\n");

                break;

            default:
                printf("main: Bad command!\n");
                break;
        } // end switch
    } // end for
} // end Test()




